# _*_ coding: utf-8 _*_

from numpy import empty
from numpy.random import uniform
from numpy.random import shuffle
from time import time
from time import strftime
from time import gmtime
import matplotlib.pyplot as plt
from random import sample



def lhs_uniform(n: int, p: int):
    """
    Latin Hypercube Sampling with uniform distribution
    :param n: layers
    :param p: dimensions
    :return: a matrix of shape (n, p)
    """
    mat = empty(shape=(p, n), dtype=float)
    row = empty(shape=n, dtype=float)
    grid = 1.0 / n
    for d in range(p):
        for i in range(n):
            row[i] = uniform(low=i * grid, high=(i + 1) * grid, size=1)[0]
        shuffle(row)
        mat[d] = row
    mat = mat.reshape(n, p)
    return mat


def lhs_normal(n: int, p: int):
    """
    Latin Hypercube Sampling with normal distribution
    :param n: layers
    :param p: dimensions
    :return: a matrix of shape (p, n)
    """
    pass


class Lifecycle:
    """
        Lifecycle of an optimizer running progress
    """
    def __init__(self, iterations=None, evaluations=None, deadline=None):
        self.iterations = int(1e9) if iterations is None else iterations
        self.evaluations = int(1e9) if evaluations is None else evaluations
        self.deadline = int(1e9) if deadline is None else deadline * 60  # minutes
        self.it = 0
        self.eva = 0
        self.__start_time = time()
        self.elapsed = time() - self.__start_time
        self.__pre = self.elapsed
        self.status = 'mounted'
        self.message = None
        self.gBest = []
        self.info = {}

    def set_stopping_condition(self, iterations=None, evaluations=None, deadline=None):
        self.iterations = int(1e9) if iterations is None else iterations
        self.evaluations = int(1e9) if evaluations is None else evaluations
        self.deadline = int(1e9) if deadline is None else deadline * 60  # minutes

    def update(self, it=None, eva=None, timing=True, gBest=None):
        if it is not None:
            self.it += it
        if eva is not None:
            self.eva += eva
        if timing:
            self.elapsed = time() - self.__start_time
        if gBest is not None:
            self.gBest.append(gBest)
        self.status = 'alive'

    def check_stopping_condition(self):
        msg, stop = ['', '', ''], False
        if self.it >= self.iterations:
            msg[0] = 'iterations limitation reached. '
            stop = True
        if self.eva >= self.evaluations:
            msg[1] = 'evaluations limitation reached. '
            stop = True
        if self.elapsed >= self.deadline:
            msg[2] = 'deadline reached. '
            stop = True
        if stop:
            self.message = msg[0] + msg[1] + msg[2]
            self.status = 'closed'
            t = strftime('%H:%M:%S', gmtime(self.elapsed))
            print(f'Terminated at {self.it}, time cost {t}s')
        return stop


def slice_it(bread: list, n: int):
    """
    cut bread into slices of n size
    :param bread: the target list
    :param n: grids in each layer
    :return: slices of n size
    """
    for i in range(0, len(bread), n):
        yield bread[i:i+n]


def sample_exc(population, k, exclude):
    """
    sample population exclude the target element
    :param population:
    :param k:
    :param exclude:
    :return:
    """
    sam = -1
    while True:
        sam = sample(population, k)
        if exclude not in sam:
            break
    return sam


